home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Network Support Library
/
RoseWare - Network Support Library.iso
/
apidev
/
ndr4.exe
/
VLM.TXT
< prev
Wrap
Text File
|
1994-01-18
|
23KB
|
763 lines
/****************************************************************************
The following code was taken from the Network Developer's Resource, published
by RoseWare. All contents are Copyright (c) 1993, RoseWare. All Rights
Reserved.
This material is made available as a service for subscribers of the Network
Developer's Resource. This material is not public domain. Simply put, if you
are not a subscriber of the Network Developer's Resource, you are using this
material illegally.
Those interested in subscribing should contact RoseWare via:
CompuServe: 76711,110
Internet: 76711.110@compuserve.com
Phone: (704) 258-9166
Fax: (704) 258-9374
BBS: (704) 258-8675
US Mail: P.O. Box 8564
Asheville, NC 28814-8564
Also see the file SUBSCRIB.TXT in this ZIP file...
****************************************************************************/
==> Calling the VLM shell from Borland Pascal <==
Well, the VLM shells are out and you're wondering, "How do I program for
them? How do I make my code VLM smart?" As of the time that I am writing
this article, Novell has released very little information as to how to
interface into the VLM APIs. What little has been released has only gone
out to a few people and the information is very incomplete and not very
accurate.
But, with what information I do have and what I've managed to put
together of other prominate NetWare nerds, I've managed to get quite a
few things working. The code presented here is the basic interface into
the VLM shell and lets you at least talk to it from Pascal. Once you get
that far you can then start digging around for whatever API information
you can come up with.
And, when you finally get the API information and you start making the
new calls, you find that many of them either don't work, or barely work.
So you have to be careful to program around the bugs and try to
compensate by building smarts around a dumb API. I am currently using
VLM 1.1 and it still, in my opinion, isn't ready yet. But, it is close
enough to see the light at the end of the tunnel. Some day VLM shells
are going to work and be great. So it's worth starting to move in that
direction.
In my opinion, Novell has really failed the third party developers on
these VLM shells. API information should have been available BEFORE
these shells were ever released. So here it is almost a year AFTER and
the information is rare, inaccurate, and incomplete. Novell has made no
commitments as to when this situation will be fixed. I therefore
encourage to to complain bitterly to Novell and encourage them to fix
the problem, for their sake, as well as ours.
THE VLM INTERFACE
-----------------
The NETX shell was called by using Int 21 calls. The VLM shell is
different. The first thing you do is an Int 2F call to get the address
if the VLM API interface. Once you have that address, you use it to talk
to the VLM. If this call fails then you're running under NETX and should
then make the NETX call.
To call the VLM you push the VLM functions on the stack and call the VLM
address. The assembler listing here is code I modified from the
Turbopower Software Object Professional Library. If you're programming
in Pascal, this is a "must have" library. It has everything in it and
then some. If you don't have it already, get it.
With the NETX shells you created Request and Reply buffers with the
first two bytes being the buffer length. You then passed registers
pointing to these buffers. The VLM shells are different. Here the
registers point to a "fragment list" which is a list of pointers and
buffers lengths to send and receive data. Why you would want to do this,
I don't know. But that's the way it is.
What I did in my interface was to create a structure that is like the
old call, and translate it to the new call. I use a single fragment in
my fragment list. I still pass the Request and Reply buffers with the
first two bytes being the buffer length. But if I make a VLM call, I
read the length word into the fragment list and move the buffer pointer
forward to bytes to point to the data.
Most Netware calls use either $E1, $E2 or $E3 in the AH register. These
calls are directly translated into VLM calls $15, $16 and $17. By
looking at my examples, you can create VLM smart calls for most of your
existing functions and will allow you to make your code somewhat VLM
smart while we are still waiting for Novell to release "the good stuff".
ASSEMBLER CODE
--------------
;******************************************************
; OPVLM.ASM 1.21
; Vlm Interface routines
; Copyright (c) TurboPower Software 1987, 1992.
; Portions Copyright (c) Sunny Hill Software 1985, 1986
; and used under license to TurboPower Software
; All rights reserved.
; Portions Copyright (c) Marc Perkel / Computer Tyme 1993
;
; Computer Tyme * 417-866-1222
; TurboPower Software * 719-260-6641
;
;******************************************************
;****************************************************** Equates
;Fields in an IntRegisters variable
BpR = 0
EsR = 2
DsR = 4
DiR = 6
SiR = 8
DxR = 10
CxR = 12
BxR = 14
AxR = 16
Flags = 22
;****************************************************** Data
DATA SEGMENT BYTE PUBLIC
DATA ENDS
;****************************************************** Code
CODE SEGMENT BYTE PUBLIC
ASSUME CS:CODE, DS:DATA
PUBLIC CallVlm
;****************************************************** Structures
;Structure of a pointer
Pointer STRUC
Ofst DW 0
Segm DW 0
Pointer ENDS
;****************************************************** EmulateInt
;procedure CallVlm(var Regs : IntRegisters; IntAddr : Pointer;
; DestID, DestFun : Word);
;Emulates an interrupt by filling the CPU registers with the values in Regs,
;clearing interrupts, pushing the flags, and calling far to IntAddr.
;Equates for parameters
destFun EQU word ptr [BP+6]
destID EQU word ptr [BP+8]
IntAddr EQU DWORD PTR [BP+10]
Regs EQU DWORD PTR [BP+14]
Regs2 EQU DWORD PTR [BP+20] ;Regs after the 'interrupt' - pushed
;BP, DS, and flags figure into
;equation the second time around
;Temporary variable stored in the code segment
IntAddress Pointer <>
CallVlm PROC FAR
PUSH BP ;Save BP
MOV BP,SP ;Set up stack frame
PUSHF ;Save flags
PUSH DS ;Save DS
;Load registers with contents of Regs
PUSH 0 ;VLM stack parameters
PUSH DestID
PUSH DestFun
LDS DI,Regs ;DS:DI points to Regs
MOV AH,[DI].Flags ;Get new flags into AH
SAHF ;Load flags from AH
MOV BX,[DI].BxR ;Load BX
MOV CX,[DI].CxR ;Load CX
MOV DX,[DI].DxR ;Load DX
;Set up for the far call with interrupts off -- this section of code
;is not re-entrant
LES SI,IntAddr ;ES:SI points to IntAddr
CLI ;Interrupts off
PUSH DS
PUSHF
MOV AX,SEG DATA
MOV DS,AX
MOV AX,CS
MOV DS,AX
MOV DS:IntAddress.Ofst,SI ;Save offset of IntAddr
MOV DS:IntAddress.Segm,ES ;Save segment
POPF
POP DS
MOV AX,[DI].AxR ;Load AX
MOV BP,[DI].BpR ;Load BP
MOV SI,[DI].SiR ;Load SI
MOV ES,[DI].EsR ;Load ES
PUSH [DI].DsR ;PUSH new DS
MOV DI,[DI].DiR ;Load DI
POP DS ;POP new DS
;Emulate interrupt
CALL DWORD PTR CS:IntAddress ;Call IntAddr
;Get ready to load Regs
PUSH BP ;Save BP
MOV BP,SP ;Set up stack frame
PUSHF ;Save Flags
PUSH ES ;Save ES
PUSH DI ;Save DI
;Load Regs with new values
LES DI,Regs2 ;ES:DI points to Regs
ADD DI,AxR ;ES:DI points to Regs.AX
STD ;Go backward
STOSW ;Store AX
MOV AX,BX ;Get BX into AX and store it
STOSW
MOV AX,CX ;Get CX into AX and store it
STOSW
MOV AX,DX ;Get DX into AX and store it
STOSW
MOV AX,SI ;Get SI into AX and store it
STOSW
POP AX ;POP saved DI into AX
STOSW
MOV AX,DS ;Get DS into AX and store it
STOSW
POP AX ;POP saved ES into AX
STOSW
;ES:DI now points to Regs.BP
POP AX ;POP saved Flags into AX
POP ES:[DI] ;POP saved BP into place
MOV ES:[DI].Flags,AL ;Store low byte of flags
CLD ;Clear direction flag
;Clean up and return
POP DS ;Restore DS
POPF ;Restore flags
POP BP ;Restore BP
RET 12
CallVlm ENDP
CODE ENDS
END
PASCAL INTERFACE CODE
---------------------
This code compiles a Turbo Pascal Unit that you include with your
program. By using the TransportCallEx procedures you should be able to
make your programs somewhat VLM smart.
{Novell VLM Shell Interface Routines
;******************************************************
; VLM.PAS
; Vlm Interface routines
; Copyright (c) TurboPower Software 1987, 1992.
; Portions Copyright (c) Sunny Hill Software 1985, 1986
; and used under license to TurboPower Software
; All rights reserved.
; Portions Copyright (c) Marc Perkel / Computer Tyme 1993
;
; Computer Tyme * 417-866-1222
; TurboPower Software * 719-260-6641
;
;******************************************************}
{$F+}
Unit VLM;
Interface
type
Dummy5 = array[1..5] of Word;
IntRegisters =
record
case Byte of
1 : (BP, ES, DS, DI, SI, DX, CX, BX, AX, IP, CS, Flags : Word);
2 : (Dummy : Dummy5; DL, DH, CL, CH, BL, BH, AL, AH : Byte);
end;
Str80 = String[80];
WordPtr = ^Word;
OS =
record
O, S : Word;
end;
var
NovResult : Byte;
VlmAddr : Pointer;
{Procedures Exported}
Procedure VlmCall (var Regs : IntRegisters; DestID, DestFunc : Word);
Procedure TransportCallE1 (Request, Reply : Pointer);
Procedure TransportCallE2 (Request, Reply : Pointer);
Procedure TransportCallE3 (Request, Reply : Pointer);
Function ConTableSize : Byte;
Function ConsoleOperator : Boolean;
Function LoginName (Conn : Word) : String;
Function NovConnection : Word;
Function NovBinderyAccess : Byte;
Function NumberOfPrinters : Byte;
Function ObjectID (ObjName : Str80; T : Word) : LongInt;
Function ObjectName (I : LongInt) : String;
Function VlmLoaded : Boolean;
Implementation
Uses
Dos;
{When this unit is initialized the variable VlmAddr is set to point to
the VLM shell call address if the VLM shell is found. If it isn't found,
the address is set ti nil.}
Function VlmLoaded : Boolean;
begin
VlmLoaded := VlmAddr <> nil;
end;
{This routine interfaces with VLMCALL.OBJ which pushes parameters
on the stack and calls the VLM Address}
{$L VLMCALL.OBJ}
Procedure CallVlm (var Regs : IntRegisters; IntAddr : Pointer;
DestID, DestFunc : Word); external;
Procedure VlmCall (var Regs : IntRegisters; DestID, DestFunc : Word);
begin
CallVlm(Regs,VlmAddr,DestID,DestFunc);
NovResult := Regs.AX;
end;
{VLM calls use server connection handles. This function returns the
connection handle of the default server.}
Function DefaultConnectionHandle : Word;
var NovRegs : IntRegisters;
begin
with NovRegs do begin
BX := 1;
VlmCall(NovRegs,$43,6);
DefaultConnectionHandle := CX;
end;
end;
{From Turbopower Software's OPINLINE.PAS program}
function PtrToLong(P : Pointer) : LongInt;
{-Convert pointer, in range $0:$0 to $FFFF:$000F, to LongInt}
begin
PtrToLong := (LongInt(OS(P).S) shl 4)+OS(P).O;
end;
function LongToPtr(L : LongInt) : Pointer;
{-Return LongInt L as a normalized pointer}
begin
LongToPtr := Ptr(Word(L shr 4), Word(L and $F));
end;
function AddLongToPtr(P : Pointer; L : LongInt) : Pointer;
{-Add a LongInt to a pointer, returning a normalized pointer}
begin
AddLongToPtr := LongToPtr(L+PtrToLong(P));
end;
{When using NETX calls, the DS:SI registers point to the Request Buffer
and ES:DI points the the Reply Buffer. The first word of these buffers
is the buffer size.
The VLM shells are different. The DS:SI registers point to a fragment list
containing up to 5 fragments for the data to be sent, and ES:DI points
to a fragment list of up to 5 fragments to receive data. Why Novell does
this escapes me. So I create a list with a single request and reply
fragment.
These fragments no longer have the fragment length as the first word and
this makes it difficult to make a single interface that is smart enough
to make either a VLM call or a NetX call. What I do is build a Request
buffer with the first word for the length. If I then make a VLM call,
I move the pointer two bytes forward past the length word and copy the
length word into the fragment list. This way the old request and reply
buffers still work with the new VLM shells.}
var
ReqBuf : Pointer;
ReqBufSize : Word;
RepBuf : Pointer;
RepBufSize : Word;
Procedure VLMTransportCall (Request, Reply : Pointer; Func : Word);
var
NovRegs : IntRegisters;
P : Pointer;
begin
ReqBuf := Request;
ReqBufSize := WordPtr(Request)^;
RepBuf := AddLongToPtr(Reply,2);
RepBufSize := WordPtr(Reply)^;
with NovRegs do begin
CX := DefaultConnectionHandle;
DS := Seg(ReqBuf);
SI := Ofs(ReqBuf);
ES := Seg(RepBuf);
DI := Ofs(RepBuf);
BX := 0;
DX := 0;
if ReqBufSize > 0 then BX := 1;
if RepBufSize > 0 then DX := 1;
AX := Func;
end;
VlmCall(NovRegs,$20,6);
end;
Procedure NetxTransportCall (Request, Reply : Pointer; Func : Byte);
var Regs : Registers;
begin
with Regs do begin
DS := Seg(Request^);
SI := Ofs(Request^);
ES := Seg(Reply^);
DI := Ofs(Reply^);
AH := Func;
MsDos(Regs);
NovResult := AL;
end;
end;
{All $E1 transport calls are mapped to VLM calls 21. Likewise, $E2 is
mapped to 22, and $E3 is mapped to 23. The difference in call numbers
is a value of $CC.}
Procedure TransportCall (Request, Reply : Pointer; B : Byte);
begin
if VlmLoaded then begin
VlmTransportCall(Request,Reply,B - $CC);
end else begin
NetxTransportCall(Request,Reply,B);
end;
end;
Procedure TransportCallE1 (Request, Reply : Pointer);
begin
TransPortCall(Request,Reply,$E1);
end;
Procedure TransportCallE2 (Request, Reply : Pointer);
begin
TransPortCall(Request,Reply,$E2);
end;
Procedure TransportCallE3 (Request, Reply : Pointer);
begin
TransPortCall(Request,Reply,$E3);
end;
Function ConTableSize : Byte;
{Returns the size of the connection table}
var NovRegs : IntRegisters;
begin
with NovRegs do begin
if VlmLoaded then begin
VlmCall(NovRegs,$10,$0F);
ConTableSize := DX;
end else ConTableSize := 8;
end;
end;
{The VLM shell allows up to 9 printers. (LPT1-LPT9)}
Function NumberOfPrinters : Byte;
var NovRegs : IntRegisters;
begin
if VlmLoaded then with NovRegs do begin
BX := 1;
VlmCall(NovRegs,$42,7);
if AL = 0 then NumberOfPrinters := BX else NumberOfPrinters := 3;
end else NumberOfPrinters := 3;
end;
Function NovConnection : Word;
{Returns the 16 bit connection number}
var Regs : Registers;
NovRegs : IntRegisters;
begin
if VlmLoaded then begin
with NovRegs do begin
CX := DefaultConnectionHandle;
BH := 13;
VlmCall(NovRegs,$10,7);
NovConnection := DX;
end;
end else begin
with Regs do begin
AH := $DC;
MsDos(Regs);
NovConnection := AL;
end;
end;
end;
Function NovBinderyAccess : Byte;
var Request : record
Len : Word;
Fun : Byte;
end;
Reply : record
Len : Word;
Access : Byte;
Obj : LongInt;
end;
begin
if NovConnection = 0 then begin
NovBinderyAccess := 0;
exit;
end;
Request.Len := SizeOf(Request);
Request.Fun := $46;
Reply.Len := SizeOf(Reply);
TransportCallE3(@Request,@Reply);
NovBinderyAccess := Reply.Access and 15;
end;
Procedure ReverseWord (var W : Word);
var A : Array[1..2] of Byte absolute W;
B : Byte;
begin
B := A[1];
A[1] := A[2];
A[2] := B;
end;
Procedure ReverseLongInt (var L : LongInt);
var A : Array[1..4] of Byte absolute L;
B : Byte;
begin
B := A[1];
A[1] := A[4];
A[4] := B;
B := A[2];
A[2] := A[3];
A[3] := B;
end;
Procedure FixString (var St : String);
begin
St[0] := #255;
St[0] := char(pred(pos(#0,St)));
end;
Function LoginName (Conn : Word) : String;
var Request : record
Len : Word;
Fun : Byte;
Connection : LongInt;
end;
Reply : record
Len : Word;
ID : LongInt;
Typ : Word;
Name : Array[1..48] of byte;
Time : Array[1..10] of Byte;
end;
St : String;
begin
Request.Len := SizeOf(Request);
if VlmLoaded then Request.Fun := $1C else Request.Fun := $16;
Request.Connection := Conn;
Reply.Len := SizeOf(Reply);
TransportCallE3(@Request,@Reply);
move(Reply.Name,St[1],48);
FixString(St);
LoginName := St;
end;
Function ObjectName (I : LongInt) : String;
var Request : record
Len : Word;
Fun : Byte;
ID : LongInt;
end;
Reply : record
Len : Word;
ID : LongInt;
Typ : Word;
Name : Array[1..48] of Byte;
end;
St : String;
begin
with Request do begin
Len := SizeOf(Request);
Fun := $36;
ID := I;
end;
Reply.Len := SizeOf(Reply);
TransportCallE3(@Request,@Reply);
move(Reply.Name,St[1],48);
FixString(St);
ObjectName := St;
end;
Function ObjectID (ObjName : Str80; T : Word) : LongInt;
var Request : record
Len : Word;
Fun : Byte;
Typ : Word;
Name : String[48]
end;
Reply : record
Len : Word;
ID : LongInt;
Typ : Word;
Name : Array[1..48] of Byte;
end;
begin
ObjectID := 0;
with Request do begin
Len := SizeOf(Request);
Fun := $35;
Typ := T;
ReverseWord(Typ);
Name := ObjName;
end;
Reply.Len := SizeOf(Reply);
TransportCallE3(@Request,@Reply);
if NovResult = 0 then ObjectID := Reply.ID;
end;
Function ConsoleOperator : Boolean;
var Request : record
Len : Word;
Fun : Byte;
end;
Reply : Word;
begin
with Request do begin
Len := SizeOf(Request);
Fun := $C8;
end;
Reply := 0;
TransportCallE3(@Request,@Reply);
ConsoleOperator := NovResult = 0;
end;
Procedure InitVlm;
var W : Array[1..2] of Word absolute VlmAddr;
Regs : Registers;
begin
VlmAddr := nil;
with Regs do begin
AX := $7A20;
BX := 0;
Intr($2F,Regs);
if AX = 0 then begin
W[1] := BX;
W[2] := ES;
end;
end;
end;
begin
InitVlm;
end.
EVERYTHING HAS A PRICE
----------------------
This code can be distributed free with no charge or royalty under the
following conditions:
1) If you produce a commercial or shareware product you send us a free
copy of the programs for our own use and provide free upgrades upon
request.
2) If you write a book using this information you send us each 2 free
copies of the book when published. You also have to credit us so
that we have a little something for our ego.
You can reach Kim Kokkonen at:
TurboPower Software
P.O. Box 49009
Colorado Springs, CO 80949-9009
719-260-6641 (voice, Monday-Friday 9AM-5PM Mountian Time)
719-260-7151 (fax)
719-260-9726 (bbs)
Compuserve: 76004,2611 PCVENB Section 6
You can reach Marc Perkel at:
Computer Tyme
411 North Sherman, Suite 300
Springfield Mo. 65802
417-866-1222 voice
417-866-1665 bbs/fax
Compuserve 71333,427 NVENA Section 3
MHS: Marc @ CTyme
Internet: Marc @ CTyme.MHS.Compuserve.com